package com.md.infrastructure.util;

import com.google.common.collect.Lists;
import com.md.exception.ServerException;
import com.md.infrastructure.config.AzureSamlConfig;
import com.md.infrastructure.config.JumpEntity;
import com.md.infrastructure.configuration.AzureSamlCoreConfiguration;
import com.md.util.HttpUtil;
import com.md.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import org.joda.time.DateTime;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.common.messaging.context.SAMLEndpointContext;
import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.binding.encoding.impl.HTTPRedirectDeflateEncoder;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.core.NameIDType;
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml.saml2.metadata.Endpoint;
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
import org.opensaml.saml.saml2.metadata.impl.SingleSignOnServiceBuilder;
import org.opensaml.saml.security.impl.SAMLMetadataSignatureSigningParametersResolver;
import org.opensaml.security.SecurityException;
import org.opensaml.security.credential.BasicCredential;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialSupport;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.xmlsec.SignatureSigningParameters;
import org.opensaml.xmlsec.SignatureSigningParametersResolver;
import org.opensaml.xmlsec.context.SecurityParametersContext;
import org.opensaml.xmlsec.criterion.SignatureSigningConfigurationCriterion;
import org.opensaml.xmlsec.impl.BasicSignatureSigningConfiguration;
import org.opensaml.xmlsec.keyinfo.KeyInfoGeneratorManager;
import org.opensaml.xmlsec.keyinfo.NamedKeyInfoGeneratorManager;
import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.support.SignatureConstants;
import org.opensaml.xmlsec.signature.support.SignatureException;
import org.opensaml.xmlsec.signature.support.SignatureSupport;
import org.w3c.dom.Element;

import javax.xml.transform.TransformerException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;

/**
 * @author 刘骄阳
 * 2024-03-11 23:46
 */
@Slf4j
public class SamlUtil {

  public static Endpoint getIPDEndpoint() {
    SingleSignOnService endpoint = new SingleSignOnServiceBuilder().buildObject();
    endpoint.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
    // 目标地址
    endpoint.setLocation("https://login.microsoftonline.com/00618cc4-0e79-43ef-9df6-41ddc9a38d62/saml2");

    return endpoint;
  }

  /**
   * @param idpSsoUrl  目标地址，即 IDP 接收请求的地址
   * @param acsUrl     回调地址
   * @param spEntityId 应用ID
   * @return SamlRequest
   * @throws TransformerException
   * @throws MarshallingException
   */
  public static String createRequestXmlString(final String idpSsoUrl,
                                              final String acsUrl,
                                              final String spEntityId) throws Exception {
    AuthnRequest authnRequest = createRequest(idpSsoUrl, acsUrl, spEntityId);
    HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();
    MessageContext<SAMLObject> context = new MessageContext<>();
    context.setMessage(authnRequest);
//    authnRequest.setSignature();


    //关于传输对端实体的信息，对于IDP就是SP，对于SP就是IDP；
    SAMLPeerEntityContext peerEntityContext =
      context.getSubcontext(SAMLPeerEntityContext.class, true);

    //端点信息；
    SAMLEndpointContext endpointContext =
      peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
    endpointContext.setEndpoint(getIPDEndpoint());

//    //数据签名环境上线文
//    SignatureSigningParameters signatureSigningParameters = new SignatureSigningParameters();
//    //获得证书，其中包含公钥
//    signatureSigningParameters.setSigningCredential(SPCredentials.getCredential());
//    //ALGO_ID_SIGNATURE_RSA_SHA256
//    signatureSigningParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
//    X509Certificate

    context.getSubcontext(SecurityParametersContext.class, true);
//        .setSignatureSigningParameters(signatureSigningParameters);


    encoder.setMessageContext(context);
    encoder.setHttpServletResponse(HttpUtil.getHttpServletResponse());


    try {
      encoder.initialize();
      //*encode*方法将会压缩消息，生成签名，添加结果到URL并从定向用户到Idp.
      //先使用RFC1951作为默认方法压缩数据，在对压缩后的数据信息Base64编码
      encoder.encode();
    } catch (ComponentInitializationException e) {
      throw new RuntimeException(e);
    }
    return "";
  }

  /**
   * 创建AutheRequest对象
   *
   * @param idpSsoUrl  目标地址，即 IDP 接收请求的地址
   * @param acsUrl     回调地址
   * @param spEntityId 应用ID
   * @return
   */
  public static AuthnRequest createRequest(final String idpSsoUrl, final String acsUrl, final String spEntityId) {
    // 使用Builder设置属性
    AuthnRequest authnRequest = AzureSamlCoreConfiguration.AUTHN_REQUEST_BUILDER.buildObject();

    // 是否强制校验即每次都会进行校验
    authnRequest.setForceAuthn(Boolean.FALSE);
    // 是否用户无感知
    authnRequest.setIsPassive(Boolean.FALSE);


    // 如果需要签名（此处仅为示例，实际签名过程会更复杂）
    Signature signature = (Signature) XMLObjectProviderRegistrySupport.getBuilderFactory()
      .getBuilder(Signature.DEFAULT_ELEMENT_NAME)
      .buildObject(Signature.DEFAULT_ELEMENT_NAME);
//    signature.setSigningCredential(""); // 设置签名凭据
    signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);

    // 将签名添加到AuthnRequest中
    authnRequest.setSignature(signature);

    authnRequest.setIssueInstant(new DateTime());
    authnRequest.setDestination(idpSsoUrl);
    authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);

    authnRequest.setID("A" + UUID.randomUUID());
    authnRequest.setAssertionConsumerServiceURL(acsUrl);


    authnRequest.setIssuer(createIssuer(spEntityId));

    authnRequest.setNameIDPolicy(createNameIDPolicy());
    return authnRequest;
  }

  public static AuthnRequest createRequest(final AzureSamlConfig azureSamlConfig) {
    AuthnRequestBuilder authnRequestBuilder = new AuthnRequestBuilder();
    // 使用Builder设置属性
    AuthnRequest authnRequest = authnRequestBuilder.buildObject();

    // 是否强制校验即每次都会进行校验
    authnRequest.setForceAuthn(Boolean.FALSE);
    // 是否用户无感知
    authnRequest.setIsPassive(Boolean.FALSE);
    authnRequest.setIssueInstant(new DateTime());

    // 配置跳转地址
    JumpEntity jumpEntity = azureSamlConfig.getSignOnServiceBinding();
    authnRequest.setProtocolBinding(jumpEntity.getMethod());
    authnRequest.setDestination(jumpEntity.getUrl());
    authnRequest.setAssertionConsumerServiceURL(jumpEntity.getResponseUrl());

    authnRequest.setID("A" + UUID.randomUUID());


    authnRequest.setIssuer(createIssuer("chinardic-dev.kraftheinz.com.cn"));
//    authnRequest.setIssuer(createIssuer("a2c39ba2-3cc0-4a44-acaa-20b96dc35ebd"));

    authnRequest.setNameIDPolicy(createNameIDPolicy());
    if (azureSamlConfig.getNeedSigned()) {
      sign(authnRequest, azureSamlConfig);
    }
    return authnRequest;
  }

  /**
   * SAML协议中是用来标识发送消息的实体（例如，Identity Provider或Service Provider）的一个XML元素。
   * 在处理SAML断言或请求时，通常需要创建和配置Issuer对象，以表明消息的来源。
   *
   * @param spEntityId 应用id
   * @return IssuerBuilder
   */
  private static Issuer createIssuer(final String spEntityId) {
    IssuerBuilder issuerBuilder = new IssuerBuilder();
    Issuer issuer = issuerBuilder.buildObject();
    issuer.setValue(spEntityId);
    return issuer;
  }

  /**
   * 在SAML身份验证请求（AuthnRequest）中指示期望的响应中返回的主体名称ID（NameID）的具体格式和属性。
   * 它允许服务提供者（SP）告诉身份提供者（IdP）如何生成或选择NameID，以便在SAML断言中返回。
   *
   * @return NameIDPolicy
   */
  private static NameIDPolicy createNameIDPolicy() {
    NameIDPolicyBuilder nameIDPolicyBuilder = new NameIDPolicyBuilder();
    NameIDPolicy nameIDPolicy = nameIDPolicyBuilder.buildObject();
    nameIDPolicy.setAllowCreate(true);
    nameIDPolicy.setFormat(NameIDType.UNSPECIFIED);
    return nameIDPolicy;
  }

  private static Signature createSignature() {
    return null;
  }

  /**
   * 生成包含 X.509 证书信息的 KeyInfo 元素
   *
   * @return 证书管理
   */
  public static NamedKeyInfoGeneratorManager createSignatureKeyInfoGeneratorManager() {
    final NamedKeyInfoGeneratorManager namedManager = new NamedKeyInfoGeneratorManager();
    // 设置 NamedKeyInfoGeneratorManager 使用默认的管理器
    namedManager.setUseDefaultManager(true);
    final KeyInfoGeneratorManager defaultManager = namedManager.getDefaultManager();
    // Generator for X509Credentials
    final X509KeyInfoGeneratorFactory x509Factory = new X509KeyInfoGeneratorFactory();
    // 生成 KeyInfo 时包含实体证书和证书链。
    x509Factory.setEmitEntityCertificate(true);
    x509Factory.setEmitEntityCertificateChain(true);
    defaultManager.registerFactory(x509Factory);
    return namedManager;
  }

  /**
   * 对请求登录请求进行验签
   *
   * @param authnRequest 用于登录的request
   */
  public static void sign(final AuthnRequest authnRequest, final AzureSamlConfig azureSamlConfig) {
    try {
      SignatureSupport.signObject(authnRequest, resolveSigningParameters(azureSamlConfig));
    } catch (SecurityException | SignatureException | MarshallingException e) {
      // 验签失败
      throw ServerException.build(ErrorEnum.DECODING_ERROR, e);
    }
  }

  public static List<Credential> resolveSigningCredentials(final AzureSamlConfig azureSamlConfig) {
    List<Credential> credentials = Lists.newArrayList();
    for (X509Certificate certificate : azureSamlConfig.getVerificationCertificates()) {
      BasicCredential credential = CredentialSupport.getSimpleCredential(certificate, null);
      credential.setEntityId(azureSamlConfig.getEntityId());
      credential.setUsageType(UsageType.SIGNING);
      credentials.add(credential);
    }
    return credentials;
  }

  public static SignatureSigningParameters resolveSigningParameters(final AzureSamlConfig azureSamlConfig) {
    List<Credential> credentials = resolveSigningCredentials(azureSamlConfig);
    SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
    CriteriaSet criteria = new CriteriaSet();
    BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration();
    signingConfiguration.setSigningCredentials(credentials);
    signingConfiguration.setSignatureAlgorithms(azureSamlConfig.getSigningAlgorithms());
    signingConfiguration.setSignatureReferenceDigestMethods(Lists.
      newArrayList(SignatureConstants.ALGO_ID_DIGEST_SHA256));
    signingConfiguration.setSignatureCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    signingConfiguration.setKeyInfoGeneratorManager(SamlUtil.createSignatureKeyInfoGeneratorManager());
    criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
    try {
      SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
      if (ObjectUtil.isEmpty(parameters)) {
        // 签名失败
        throw ServerException.build(ErrorEnum.DECODING_ERROR);
      }
      return parameters;
    } catch (Exception ex) {
      throw ServerException.build(ErrorEnum.DECODING_ERROR, ex);
    }
  }

  public static String serialize(final AuthnRequest authnRequest) {
    try {
      Element element = AzureSamlCoreConfiguration.MARSHALLER.marshall(authnRequest);
      return SerializeSupport.nodeToString(element);
    } catch (MarshallingException ex) {
      throw ServerException.build(ErrorEnum.DECODING_ERROR, ex);
    }
  }

  public static void main(String[] args) {
    createRequestXmlString1("", "http://localhost:8090/saml/callback/", "chinardic-dev.kraftheinz.com.cn");
  }

  /**
   * @param idpSsoUrl  目标地址，即 IDP 接收请求的地址
   * @param acsUrl     回调地址
   * @param spEntityId 应用ID
   * @return SamlRequest
   * @throws TransformerException
   * @throws MarshallingException
   */
  public static String createRequestXmlString1(final String idpSsoUrl,
                                               final String acsUrl,
                                               final String spEntityId) throws Exception {
    AuthnRequest authnRequest = createRequest(idpSsoUrl, acsUrl, spEntityId);
    HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();
    MessageContext<SAMLObject> context = new MessageContext<>();
    context.setMessage(authnRequest);
//    authnRequest.setSignature();


    //关于传输对端实体的信息，对于IDP就是SP，对于SP就是IDP；
    SAMLPeerEntityContext peerEntityContext =
      context.getSubcontext(SAMLPeerEntityContext.class, true);

    //端点信息；
    SAMLEndpointContext endpointContext =
      peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
    endpointContext.setEndpoint(getIPDEndpoint());

//    //数据签名环境上线文
//    SignatureSigningParameters signatureSigningParameters = new SignatureSigningParameters();
//    //获得证书，其中包含公钥
//    signatureSigningParameters.setSigningCredential(SPCredentials.getCredential());
//    //ALGO_ID_SIGNATURE_RSA_SHA256
//    signatureSigningParameters.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);

    context.getSubcontext(SecurityParametersContext.class, true);
//        .setSignatureSigningParameters(signatureSigningParameters);


    encoder.setMessageContext(context);
    encoder.setHttpServletResponse(HttpUtil.getHttpServletResponse());


    try {
      encoder.initialize();
      //*encode*方法将会压缩消息，生成签名，添加结果到URL并从定向用户到Idp.
      //先使用RFC1951作为默认方法压缩数据，在对压缩后的数据信息Base64编码
      encoder.encode();
    } catch (ComponentInitializationException e) {
      throw new RuntimeException(e);
    }
    return "";
  }

  public Signature createAndSignSignature(Element toBeSigned, X509Credential signingCredential) throws Exception {
    // 创建Signature对象
//    Signature signature = (Signature) Configuration.getBuilderFactory()
//        .getBuilder(Signature.DEFAULT_ELEMENT_NAME)
//        .buildObject(Signature.DEFAULT_ELEMENT_NAME);

//    // 设置签名算法
//    signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
//
//    // 设置CanonicalizationMethod
//    signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
//
//    // 设置Reference元素（指向要签名的XML元素）
//    // ... 创建和配置Reference元素
//
//    // 将Signature添加到要签名的文档中
//    toBeSigned.getParentNode().insertBefore(signature.getDOM(), toBeSigned);
//
//    // 创建Signer实例并执行签名
//    Signer signer = new Signer();
//    signer.setSigningCredential(signingCredential);
//    signer.setSignature(signature);
//    signer.setDocument((Document) toBeSigned.getOwnerDocument());
//
//    // 执行签名操作
//    signer.sign();
//
//    // 返回已签名的Signature对象
//    return signature;
    return null;
  }

  // 验证Signature
  public boolean validateSignature(Signature signature, Credential validationCredential) throws SignatureException {
//    SignatureValidator validator = new SignatureValidator(validationCredential);
//    try {
//      validator.validate(signature);
//      return true;
//    } catch (ValidationException e) {
//      return false;
//    }
    return true;
  }

}
